#include "../../src/box/timer_factory.hh"
#include "../../src/util/os_util.hh"
#include "../../src/util/time_util.hh"
#include "../../src/util/timer_wheel.hh"
#include "../framework/unittest.hh"
#include <chrono>
#include <iostream>
#include <thread>

FIXTURE_BEGIN(test_timer_wheel)

CASE(TestSchedule1) {
  kratos::util::TimerWheel tw(nullptr);
  bool done = false;
  ASSERT_TRUE(tw.schedule(
      [&](kratos::util::TimerID, std::uint64_t) -> bool {
        done = true;
        return true;
      },
      1000, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
}

CASE(TestSchedule2) {
  kratos::util::TimerWheel tw(nullptr);
  bool done = false;
  ASSERT_FALSE(tw.schedule(
      [&](kratos::util::TimerID, std::uint64_t) -> bool {
        done = true;
        return true;
      },
      0, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_FALSE(done);
}

CASE(TestSchedule3) {
  kratos::util::TimerWheel tw(nullptr);
  int count = 0;
  ASSERT_TRUE(tw.schedule(
      [&](kratos::util::TimerID, std::uint64_t) -> bool {
        count += 1;
        return true;
      },
      500, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 2100) {
      break;
    }
  }
  ASSERT_TRUE(count == 4);
}

CASE(TestScheduleOnce1) {
  kratos::util::TimerWheel tw(nullptr);
  bool done = false;
  ASSERT_TRUE(tw.schedule_once(
      [&](kratos::util::TimerID, std::uint64_t) -> bool {
        done = true;
        return true;
      },
      1000, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
}

CASE(TestScheduleOnce2) {
  kratos::util::TimerWheel tw(nullptr);
  int count = 0;
  for (int i = 0; i < 1000; i++) {
    auto duration = kratos::util::get_random_uint32(1, 3000);
    ASSERT_TRUE(tw.schedule_once(
        [&](kratos::util::TimerID, std::uint64_t) -> bool {
          count += 1;
          return true;
        },
        duration, 0));
  }
  for (int i = 0; i < 1000; i++) {
    auto duration = kratos::util::get_random_uint32(4000, 10000);
    ASSERT_TRUE(tw.schedule_once(
        [&](kratos::util::TimerID, std::uint64_t) -> bool {
          count += 1;
          return true;
        },
        duration, 0));
  }
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 3100) {
      break;
    }
  }
  ASSERT_TRUE(count == 1000);
}

CASE(TestScheduleOnce3) {
  kratos::util::TimerWheel tw(nullptr);
  int count = 0;
  for (int i = 0; i < 1000; i++) {
    auto duration = kratos::util::get_random_uint32(1, 3000);
    ASSERT_TRUE(tw.schedule_once(
        [&](kratos::util::TimerID, std::uint64_t) -> bool {
          count += 1;
          return true;
        },
        duration, 0));
  }
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 20000) {
      break;
    }
  }
  ASSERT_TRUE(count == 1000);
}

CASE(TestCancel1) {
  kratos::util::TimerWheel tw(nullptr);
  bool done = false;
  kratos::util::TimerID id;
  ASSERT_TRUE(id = tw.schedule_once(
                  [&](kratos::util::TimerID, std::uint64_t) -> bool {
                    done = true;
                    tw.cancel(id);
                    return true;
                  },
                  500, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
}

CASE(TestCancel2) {
  kratos::util::TimerWheel tw(nullptr);
  bool done = false;
  int count = 0;
  kratos::util::TimerID id;
  ASSERT_TRUE(id = tw.schedule(
                  [&](kratos::util::TimerID, std::uint64_t) -> bool {
                    done = true;
                    count += 1;
                    tw.cancel(id);
                    return true;
                  },
                  500, 0));
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    tw.update(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
  ASSERT_TRUE(count == 1);
}

CASE(TestAddTimerOnce1) {
  kratos::service::TimerImpl timer(nullptr);
  bool done = false;
  timer.addTimerOnce([&](std::uint64_t data) { done = true; }, 500, 0);
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    timer.runOnce(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
}

CASE(TestAddTimerOnce2) {
  kratos::service::TimerImpl timer(nullptr);
  bool done = false;
  TimerHandle timer_id = timer.addTimerOnce(
      [&](std::uint64_t data) {
        timer.cancelTimer(timer_id);
        done = true;
      },
      500, 0);
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    timer.runOnce(now);
    if (now - start > 1100) {
      break;
    }
  }
  ASSERT_TRUE(done);
}

CASE(TestAddTimerOnce3) {
  kratos::service::TimerImpl timer(nullptr);
  bool done = false;
  TimerHandle timer_id = timer.addTimerOnce(
      [&](std::uint64_t data) {
        timer.cancelTimer(timer_id);
        done = true;
      },
      500, 0);
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    timer.runOnce(now);
    if (now - start > 1100) {
      break;
    }
  }
  timer.cancelTimer(timer_id);
  ASSERT_TRUE(done);
}

CASE(TestAddTimerOnce4) {
  kratos::service::TimerImpl timer(nullptr);
  bool done = false;
  for (int i = 0; i < 1000; i++) {
    TimerHandle timer_id =
        timer.addTimerOnce([&](std::uint64_t data) { done = true; }, 500, 0);
    timer.cancelTimer(timer_id);
  }
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    timer.runOnce(now);
    if (now - start > 10000) {
      break;
    }
  }
  ASSERT_TRUE(!done);
}

FIXTURE_END(test_timer_wheel)
